package net.butfly.bus.client;

import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import net.butfly.bus.TX;
import net.butfly.bus.client.connection.ConnectionFactory.Type;
import net.butfly.bus.config.bean.ConnectionBean;
import net.butfly.bus.config.bean.FilterBean;
import net.butfly.bus.config.bean.NodeBean;
import net.butfly.bus.context.Constants;
import net.butfly.bus.exception.BusRuntimeException;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

class ClientConfig {
	private final static String configFileName = "bus-client.xml";
	private static Document configDoc;
	private static Map<String, NodeBean> nodeMap;
	private static List<FilterBean> filterBeanList;
	private static String txAnnotation;

	@SuppressWarnings("unchecked")
	public ClientConfig() {
		if (configDoc == null) {
			URL url = null;
			try {
				// classpath first
				url = Thread.currentThread().getContextClassLoader().getResource(configFileName);
				if (url == null) {
					url = ClientConfig.class.getResource(configFileName);
				}

				configDoc = url != null ? new SAXReader().read(url) : null;

				if (configDoc != null) {
					// filters analyzing
					List<Element> eleList = configDoc.getRootElement().selectNodes("filter");
					if (eleList != null) {
						filterBeanList = new ArrayList<FilterBean>();
						int order = -1;
						Map<String, String> initParamMap = null;
						for (Element ele : eleList) {
							if (ele.attributeValue("enabled") == null
									|| ele.attributeValue("enabled").trim().toUpperCase().equals("TRUE")) {
								String title = ele.attributeValue("title");
								String ts = ele.attributeValue("order");
								if (ts != null) {
									ts = ts.toUpperCase();
									order = (ts.toUpperCase().startsWith("B") ? -1 : 1)
											* Integer.valueOf((ts.substring(1)));
								}
								List<Element> paramEList = ele.selectNodes("init/param");
								if (paramEList != null) {
									initParamMap = new HashMap<String, String>();
									for (Element tEle : paramEList) {
										initParamMap.put(tEle.attributeValue("name"), tEle.attributeValue("value"));
									}
								}
								filterBeanList.add(new FilterBean(title, order, ele.attributeValue("exceptionHandler"),
										initParamMap, ele.attributeValue("class")));
							}
						}
					}
					// nodes analyzing
					List<Element> nodeList = configDoc.getRootElement().selectNodes("node");
					if (nodeList != null) {
						nodeMap = new HashMap<String, NodeBean>();
						for (Element nodeEle : nodeList) {
							String nodeId = nodeEle.attributeValue("id");

							String connectionId = nodeEle.attributeValue("connection");
							Element connectionEle;
							if (connectionId != null) {
								connectionEle = (Element) configDoc.getRootElement().selectSingleNode(
										"connection[@id='" + connectionId + "']");
								if (connectionEle == null) {
									throw new BusRuntimeException(Constants.UserError.NODE_CONNECTION_NOT_FOUND,
											"没有找到 id为\"" + nodeId + "\"的 node 所配置的 id为 \"" + connectionId
													+ "\" 的 connection");
								}

							} else {
								connectionEle = (Element) nodeEle.selectSingleNode("connection");
								if (connectionEle == null) {
									throw new BusRuntimeException(Constants.UserError.NODE_CONNECTION_NOT_FOUND,
											"id为\"" + nodeId + "\"的 node 没有配置 connection");
								}
							}

							String connectionType = connectionEle.attributeValue("type");
							if (connectionType == null) {
								throw new BusRuntimeException(Constants.UserError.NODE_CONNECTION_NOT_FOUND, "id为 \""
										+ connectionId + "\" 的 connection 没有配置 type 属性");
							}

							Object connectionConfigObj = Type.parse(connectionType).getPrototype();
							if (connectionConfigObj != null) {
								try {
									processConfigObj(connectionConfigObj, connectionEle);
								} catch (Exception ex) {
									throw new BusRuntimeException(Constants.UserError.NODE_CONNECTION_NOT_FOUND,
											"设置id为 " + connectionId + " 的 join 参数出错", ex);
								}
							}
							ConnectionBean connectionBean = new ConnectionBean(connectionId, connectionType,
									connectionConfigObj);
							nodeMap.put(nodeId, new NodeBean(nodeId, nodeEle.attributeValue("txMatching"),
									connectionBean, null, null));
						}
					}

					Element ele = (Element) configDoc.getRootElement().selectSingleNode("tx-annotation");
					if (ele != null) {
						txAnnotation = ele.getTextTrim();
					}
				}
			} catch (DocumentException _e) {
				throw new BusRuntimeException(Constants.UserError.CONFIG_ERROR, "解析配置文件 " + url + " 异常", _e);
			}
		}
	}

	public NodeBean getNodeBean(String nodeID) {
		return nodeMap != null ? nodeMap.get(nodeID) : null;
	}

	public String[] getNodeIdsByTxMatching(String txCode) {
		if (nodeMap != null) {
			Vector<String> v = new Vector<String>();
			for (Entry<String, NodeBean> et : nodeMap.entrySet()) {
				if (et.getValue().matchTx(txCode)) {
					v.add(et.getValue().getId());
				}
			}
			if (v.size() > 0) {
				String[] rtn = new String[v.size()];
				rtn = v.toArray(rtn);
				return rtn;
			}
		}
		return null;
	}

	public static List<FilterBean> getFilterBeanList() {
		return filterBeanList;
	}

	public static String getTxAnnotation() {
		return txAnnotation;
	}

	public static String getServiceKey(TX tx) {
		return getServiceKey(tx.value(), tx.version());
	}

	public static String getServiceKey(String txCode, String versionNo) {
		return txCode + "-" + versionNo;
	}

	@SuppressWarnings("unchecked")
	private static void processConfigObj(Object configObject, Element element) throws IllegalArgumentException,
			IllegalAccessException {
		Class<?> clz = configObject.getClass();
		while (!clz.equals(Object.class)) {
			Field[] fAry = clz.getDeclaredFields();
			if (fAry != null) {
				for (Field f : fAry) {
					Object v = null;
					if (List.class.isAssignableFrom(f.getType())) {
						List<Element> eleList = element.selectNodes(f.getName());
						if (eleList != null) {
							List<String> itemList = new ArrayList<String>();
							for (Element ele : eleList) {
								String ts = ele.getTextTrim();
								if (!("".equals(ts))) {
									itemList.add(ts);
								}
							}
							v = itemList;
						}
					} else {
						Element ele = (Element) element.selectSingleNode(f.getName());
						if (ele != null) {
							String ts = ele.getTextTrim();
							if (!("".equals(ts))) {
								v = ts;
							}
						}
					}
					if (v != null) {
						boolean af = f.isAccessible();
						f.setAccessible(true);
						f.set(configObject, v);
						f.setAccessible(af);
					}
				}
			}
			clz = clz.getSuperclass();
		}
	}

	public String route(String txCode, String version) {
		String[] nodeIdAry = this.getNodeIdsByTxMatching(txCode);
		return nodeIdAry != null ? nodeIdAry[0] : null;
	}

	public static String getConfigFileName() {
		return configFileName + "in root of classpath or package net.butflt.bus.client";
	}

}
